

#include "functions.h"

#include <stdlib.h>

/*
 * utility functions
 */


static Relation *
get_relation_by_id( RuleEngineContext * r, Id * name )
{
    size_t          n;

    for( n = 0; n < r->relations.size; ++ n )
    {
        Relation *  rt = &r->relations.rel[n];
        if( 0 == idcmp(rt->rel_id, name) )
        {
            return rt;
        }
    }

    return 0;
}

static void
validate_static_rule( RuleEngineContext * r, EvalContext * e, List * rule )
{
    size_t      length = list_length( rule );
    Id *        rule_id;
    Relation *  rel;

    // empty list?
    if( 0 == length )
    {
        evaluator_emit_error( e, QL_ERR_RP_MALFORMED_STATIC_RULE );
    }

    // first argument isn't an identifier?
    rule_id = (Id *)rule->car;
    if( rule_id->type != OBJ_ID )
    {
        evaluator_emit_error( e, QL_ERR_RP_MALFORMED_STATIC_RULE );
    }

    // relation was registered?
    rel = get_relation_by_id( r, rule_id );
    if( !rel )
    {
        evaluator_emit_error( e, QL_ERR_RP_UNKNOWN_RELATION_IN_RULE_DECLARATION );
    }

    // does args count matches with the previously declared?
    if( rel->args_count + 1 != length )
    {
        evaluator_emit_error( e, QL_ERR_INVALID_ARGUMENTS );
    }
}

static bool
is_static_rule_exists( const RuleEngineContext * r, const List * rule )
{
    const List *        static_rules_iter = r->static_rules;

    // test whether the source storage is empty?
    if( static_rules_iter->car == 0 )
    {
        return false;
    }

    // iterate through each element
    for( ; static_rules_iter != 0; static_rules_iter = static_rules_iter->cdr )
    {
        const List *    present_rule = (List *)static_rules_iter->car;
        assert( rule->type == OBJ_LIST );

        if( 0 == listcmp(rule, present_rule) )
        {
            return true;
        }
    }
    
    return false;
}

static void
add_static_rule( RuleEngineContext * r, List * static_rule )
{
    list_push( r->alloc_ctx, r->static_rules, (Obj *)static_rule );
}


static bool
is_var( Obj * elem, List * vars )
{
    if( 0 == elem )
    {
        return false;
    }

    for( ; vars != 0; vars = vars->cdr )
    {
        if( 0 == objcmp(elem, vars->car) )
        {
            return true;
        }
    }

    return false;    
}


static bool
match_static_pattern( RuleEngineContext * r, List * source, List * pattern, List * vars )
{
    bool                result = false;

    if( vars != 0 && vars->car != 0 )
    {
        List *          it = vars;
        do
        {
            assert( it->car->type == OBJ_ID );
            setvar( r, (Id *)it->car, g_nil_obj );
            it = it->cdr;
        }
        while( it != 0 );
    }
    
    for( ;; source = source->cdr, pattern = pattern->cdr )
    {
        Obj *           s_obj;
        Obj *           p_obj;

        // come to an end of list
        if( source == 0 || pattern == 0 )
        {
            result = (source == pattern);
            break;
        }

        s_obj = source->car;
        p_obj = pattern->car;
        assert( s_obj->type == OBJ_ID && p_obj->type == OBJ_ID );

        if( s_obj == 0 || p_obj == 0 )
        {
            result = (s_obj == p_obj);
            break;
        }

        if( is_var(p_obj, vars) )
        {
            Obj * o = getvar( r, (Id *)p_obj );
            if( o->type == OBJ_NIL )
            {
                setvar( r, (Id *)p_obj, s_obj );
                continue;
            }

            result = (0 == objcmp(s_obj, o));
            break;
        }

        if( 0 != objcmp(p_obj, s_obj) )
        {
            break;
        }
    }

    return result;
}

static void
add_relation( RuleEngineContext * r, Id * name, unsigned int args_count )
{
    Relation * new_rel;
    if( get_relation_by_id(r, name) != 0 )
    {
        /* catastrophic failure - this might happen because of lack of previous checks */
        assert( !"Can't add relation - this should never happen" );
        alloc_emit_error( r->alloc_ctx, QL_ERR_FAIL );
    }

    if( r->relations.size + 1 > QL_IDOBJ_PAIRS_ARR_GROW_SIZE )
    {
        // too much relations, what is not permitted
        alloc_emit_error( r->alloc_ctx, QL_ERR_WRONG_CONF );
    }

    new_rel = &r->relations.rel[r->relations.size ++];
    
    new_rel->rel_id = name;
    new_rel->args_count = args_count;
}



static void
add_predefined_relation( RuleEngineContext * r, const char * name, unsigned int args_count )
{
    add_relation( r, alloc_id(r->alloc_ctx, name, r->default_ns), args_count );
}

void
add_predefined_relations( RuleEngineContext * r )
{
    add_predefined_relation( r, "is", 2 );
    add_predefined_relation( r, "have", 2 );
}


/*
 * implementation
 */

Obj *
f_match( void * tag, List * args )
{
    RuleEngineContext * r = tag;
    AllocContext *      ac = r->alloc_ctx;
    
    Id *                kw_vars = alloc_id( ac, "vars", 0 );
    List *              result;
    List *              it;
    List *              v_pattern = args ? (List *)args->car : 0;
    List *              v_vars = (List *)get_list_elem_by_keyword( args, kw_vars );

    // pattern shall be a list
    if( 0 == v_pattern || v_pattern->type != OBJ_LIST || !list_check_elements_type(v_pattern, OBJ_ID) )
    {
        evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
    }

    // vars array shall be just a list of identifiers
    if( 0 != v_vars && (v_vars->type != OBJ_LIST || !list_check_elements_type(v_vars, OBJ_ID)) )
    {
        evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
    }

    result = alloc_list( ac );

    // iterate through each present rule and get the match
    it = r->static_rules;
    for( ; it != 0; it = it->cdr )
    {
        assert( it->car->type == OBJ_LIST );
        if( match_static_pattern(r, (List *)it->car, v_pattern, v_vars) )
        {
            list_push( ac, result, it->car );
        }
    }

    return result->car == 0 ? g_nil_obj : (Obj *)result;
}



Obj *
f_def_static_rules( void * tag, List * args )
{
    RuleEngineContext * r = tag;
    AllocContext *      allocator = r->alloc_ctx;
    List *              list_iter = args;
    bool                one_or_more_rules_exists = false;

    for( ; list_iter != 0; list_iter = list_iter->cdr )
    {
        List *          static_rule = (List *)list_iter->car;
        if( static_rule->type != OBJ_LIST || !list_check_elements_type(static_rule, OBJ_ID) )
        {
            evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
        }

        if( !is_static_rule_exists(r, static_rule) )
        {
            validate_static_rule( r, r->eval, static_rule );
            add_static_rule( r, static_rule );
        }
        else
        {
            one_or_more_rules_exists = true;
        }
    }

    return one_or_more_rules_exists ? g_nil_obj : g_t_obj;
}


Obj *
f_get_static_rules( void * tag, List * args )
{
    RuleEngineContext * r = tag;

    if( list_length(args) != 0 )
    {
        evaluator_emit_error( r->eval, QL_ERR_INVALID_ARGUMENTS );
    }

    return (Obj *)r->static_rules;
}
